// Copyright Epic Games, Inc. All Rights Reserved.

#include "LyraPlayerSpawningManagerComponent.h"
#include "GameFramework/PlayerState.h"
#include "EngineUtils.h"
#include "Engine/PlayerStartPIE.h"
#include "LyraPlayerStart.h"

#include UE_INLINE_GENERATED_CPP_BY_NAME(LyraPlayerSpawningManagerComponent) // 包含由UE生成的代码

DEFINE_LOG_CATEGORY_STATIC(LogPlayerSpawning, Log, All); // 定义玩家生成日志类别

/**
 * 构造函数。
 */
ULyraPlayerSpawningManagerComponent::ULyraPlayerSpawningManagerComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
	SetIsReplicatedByDefault(false); // 默认不复制
	bAutoRegister = true; // 自动注册
	bAutoActivate = true; // 自动激活
	bWantsInitializeComponent = true; // 需要初始化组件
	PrimaryComponentTick.TickGroup = TG_PrePhysics; // 主组件Tick组
	PrimaryComponentTick.bCanEverTick = true; // 主组件可以Tick
	PrimaryComponentTick.bAllowTickOnDedicatedServer = true; // 主组件允许在专用服务器上Tick
	PrimaryComponentTick.bStartWithTickEnabled = false; // 主组件开始时Tick禁用
}

/**
 * 初始化组件。
 */
void ULyraPlayerSpawningManagerComponent::InitializeComponent()
{
	Super::InitializeComponent(); // 调用父类方法

	FWorldDelegates::LevelAddedToWorld.AddUObject(this, &ThisClass::OnLevelAdded); // 绑定关卡添加到世界事件

	UWorld* World = GetWorld(); // 获取世界
	World->AddOnActorSpawnedHandler(FOnActorSpawned::FDelegate::CreateUObject(this, &ThisClass::HandleOnActorSpawned)); // 添加角色生成处理器

	for (TActorIterator<ALyraPlayerStart> It(World); It; ++It) // 遍历所有Lyra玩家出生点
	{
		if (ALyraPlayerStart* PlayerStart = *It) // 获取玩家出生点
		{
			CachedPlayerStarts.Add(PlayerStart); // 添加到缓存
		}
	}
}

/**
 * 关卡添加回调。
 */
void ULyraPlayerSpawningManagerComponent::OnLevelAdded(ULevel* InLevel, UWorld* InWorld)
{
	if (InWorld == GetWorld()) // 如果是当前世界
	{
		for (AActor* Actor : InLevel->Actors) // 遍历关卡中的角色
		{
			if (ALyraPlayerStart* PlayerStart = Cast<ALyraPlayerStart>(Actor)) // 如果是Lyra玩家出生点
			{
				ensure(!CachedPlayerStarts.Contains(PlayerStart)); // 确保缓存中不包含
				CachedPlayerStarts.Add(PlayerStart); // 添加到缓存
			}
		}
	}
}

/**
 * 角色生成回调。
 */
void ULyraPlayerSpawningManagerComponent::HandleOnActorSpawned(AActor* SpawnedActor)
{
	if (ALyraPlayerStart* PlayerStart = Cast<ALyraPlayerStart>(SpawnedActor)) // 如果是Lyra玩家出生点
	{
		CachedPlayerStarts.Add(PlayerStart); // 添加到缓存
	}
}

// ALyraGameMode 代理调用 - 需要处理当有人选择
// 以引擎中的正常方式重启玩家时。
//======================================================================

/**
 * 选择玩家出生点。
 */
AActor* ULyraPlayerSpawningManagerComponent::ChoosePlayerStart(AController* Player)
{
	if (Player) // 如果控制器存在
	{
#if WITH_EDITOR
		if (APlayerStart* PlayerStart = FindPlayFromHereStart(Player)) // 查找从此处开始播放的出生点
		{
			return PlayerStart; // 返回出生点
		}
#endif

		TArray<ALyraPlayerStart*> StarterPoints; // 出生点数组
		for (auto StartIt = CachedPlayerStarts.CreateIterator(); StartIt; ++StartIt) // 遍历缓存的出生点
		{
			if (ALyraPlayerStart* Start = (*StartIt).Get()) // 获取出生点
			{
				StarterPoints.Add(Start); // 添加到数组
			}
			else
			{
				StartIt.RemoveCurrent(); // 移除无效的出生点
			}
		}

		if (APlayerState* PlayerState = Player->GetPlayerState<APlayerState>()) // 获取玩家状态
		{
			// 让专用观察者在任何随机起始位置开始，但他们不声明它
			if (PlayerState->IsOnlyASpectator()) // 如果只是观察者
			{
				if (!StarterPoints.IsEmpty()) // 如果出生点数组不为空
				{
					return StarterPoints[FMath::RandRange(0, StarterPoints.Num() - 1)]; // 返回随机出生点
				}

				return nullptr; // 返回空
			}
		}

		AActor* PlayerStart = OnChoosePlayerStart(Player, StarterPoints); // 调用选择玩家出生点回调

		if (!PlayerStart) // 如果没有选择出生点
		{
			PlayerStart = GetFirstRandomUnoccupiedPlayerStart(Player, StarterPoints); // 获取第一个随机未占用的出生点
		}

		if (ALyraPlayerStart* LyraStart = Cast<ALyraPlayerStart>(PlayerStart)) // 如果是Lyra出生点
		{
			LyraStart->TryClaim(Player); // 尝试声明出生点
		}

		return PlayerStart; // 返回出生点
	}

	return nullptr; // 返回空
}

#if WITH_EDITOR
/**
 * 查找从此处开始播放的出生点。
 */
APlayerStart* ULyraPlayerSpawningManagerComponent::FindPlayFromHereStart(AController* Player)
{
	// 只为玩家控制器'从此处开始播放'，机器人等都应该从正常出生点生成。
	if (Player->IsA<APlayerController>()) // 如果是玩家控制器
	{
		if (UWorld* World = GetWorld()) // 获取世界
		{
			for (TActorIterator<APlayerStart> It(World); It; ++It) // 遍历所有玩家出生点
			{
				if (APlayerStart* PlayerStart = *It) // 获取玩家出生点
				{
					if (PlayerStart->IsA<APlayerStartPIE>()) // 如果是PIE出生点
					{
						// 在PIE模式下，总是优先选择第一个"从此处开始播放"的PlayerStart
						return PlayerStart; // 返回出生点
					}
				}
			}
		}
	}

	return nullptr; // 返回空
}
#endif

/**
 * 检查控制器是否可以重启。
 */
bool ULyraPlayerSpawningManagerComponent::ControllerCanRestart(AController* Player)
{
	bool bCanRestart = true; // 默认为可以重启

	// TODO 他们可以重启吗？

	return bCanRestart; // 返回结果
}

/**
 * 完成重启玩家。
 */
void ULyraPlayerSpawningManagerComponent::FinishRestartPlayer(AController* NewPlayer, const FRotator& StartRotation)
{
	OnFinishRestartPlayer(NewPlayer, StartRotation); // 调用完成重启玩家回调
	K2_OnFinishRestartPlayer(NewPlayer, StartRotation); // 调用蓝图事件
}

//================================================================

/**
 * 组件每帧调用。
 */
void ULyraPlayerSpawningManagerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction)
{
	Super::TickComponent(DeltaTime, TickType, ThisTickFunction); // 调用父类方法
}

/**
 * 获取第一个随机未占用的玩家出生点。
 */
APlayerStart* ULyraPlayerSpawningManagerComponent::GetFirstRandomUnoccupiedPlayerStart(AController* Controller, const TArray<ALyraPlayerStart*>& StartPoints) const
{
	if (Controller) // 如果控制器存在
	{
		TArray<ALyraPlayerStart*> UnOccupiedStartPoints; // 未占用的出生点数组
		TArray<ALyraPlayerStart*> OccupiedStartPoints; // 已占用的出生点数组

		for (ALyraPlayerStart* StartPoint : StartPoints) // 遍历出生点
		{
			ELyraPlayerStartLocationOccupancy State = StartPoint->GetLocationOccupancy(Controller); // 获取位置占用状态

			switch (State)
			{
				case ELyraPlayerStartLocationOccupancy::Empty: // 如果为空
					UnOccupiedStartPoints.Add(StartPoint); // 添加到未占用数组
					break;
				case ELyraPlayerStartLocationOccupancy::Partial: // 如果部分占用
					OccupiedStartPoints.Add(StartPoint); // 添加到已占用数组
					break;

			}
		}

		if (UnOccupiedStartPoints.Num() > 0) // 如果有未占用的出生点
		{
			return UnOccupiedStartPoints[FMath::RandRange(0, UnOccupiedStartPoints.Num() - 1)]; // 返回随机未占用出生点
		}
		else if (OccupiedStartPoints.Num() > 0) // 如果有已占用的出生点
		{
			return OccupiedStartPoints[FMath::RandRange(0, OccupiedStartPoints.Num() - 1)]; // 返回随机已占用出生点
		}
	}

	return nullptr; // 返回空
}